package com.springsource.mvc.MethodArgumentResolver;

import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockPart;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.DefaultDataBinderFactory;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.ExpressionValueMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestHeaderMethodArgumentResolver;
import org.springframework.web.method.annotation.RequestParamMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MethodArgumentResolver {

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebConfig.class);
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

        // 准备测试Request
        HttpServletRequest multiRequest = mockRequest();


        // 要点1 控制器方法被封装为HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(new Controller(), Controller.class.getMethod("test", String.class, String.class, int.class, String.class, MultipartFile.class, int.class, String.class, String.class, String.class, HttpServletRequest.class, User.class, User.class, User.class));
        // 要点2 准备对象绑定与类型转换
        DefaultDataBinderFactory dataBinderFactory = new DefaultDataBinderFactory(null);

        // 要点3 准备ModelAndViewContainer用来存储中间Model结果
        ModelAndViewContainer container = new ModelAndViewContainer();

        // 将多个ArgumentResolver组合起来
        HandlerMethodArgumentResolverComposite argumentResolverComposite = new HandlerMethodArgumentResolverComposite();
        argumentResolverComposite.addResolvers(
                // beanFactory可以提供环境变量  false 表示必须有@RequestParam，不允许使用默认值，否则不解析
                new RequestParamMethodArgumentResolver(beanFactory, false),
                new PathVariableMapMethodArgumentResolver(),
                new RequestHeaderMethodArgumentResolver(beanFactory),
                new ServletCookieValueMethodArgumentResolver(beanFactory),
                // 解析@Value、SpEL表达式
                new ExpressionValueMethodArgumentResolver(beanFactory),
                // 解析 req\resp
                new ServletRequestMethodArgumentResolver(),
                // 把Get参数解析成对象，并放入ModelAndViewContainer
                new ServletModelAttributeMethodProcessor(false), // 必须有@ModelAttribute
                new RequestResponseBodyMethodProcessor(List.of(new MappingJackson2HttpMessageConverter())),
                new ServletModelAttributeMethodProcessor(true) // 省略了@ModelAttribute
        );

        // 要点4 解析每个参数值
        for (MethodParameter param : handlerMethod.getMethodParameters()) {
            param.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());

            String annotations = Arrays.stream(param.getParameterAnnotations()).map(anno -> anno.annotationType().getSimpleName()).collect(Collectors.joining());
            if (StrUtil.isNotEmpty(annotations)) {
                annotations = "@" + annotations + "   ";
            }
            String paramStr = param.getParameterIndex() + ":  " + annotations + param.getParameterType().getSimpleName() + " " + param.getParameterName();
            if (argumentResolverComposite.supportsParameter(param)) {
                Object o = argumentResolverComposite.resolveArgument(param, container, new ServletWebRequest(multiRequest), dataBinderFactory);
                System.out.println(paramStr + " = " + o);
            } else {
                System.out.println(paramStr);
            }
        }


    }

    private static HttpServletRequest mockRequest() {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setParameter("name1", "zhangsan");
        request.setParameter("name2", "lisi");
        request.addPart(new MockPart("file", "abc", "hello".getBytes(StandardCharsets.UTF_8)));
        Map<String, String> map = new AntPathMatcher()
                .extractUriTemplateVariables("/test/{id}", "/test/123");
        request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, map);
        request.setContentType("application/json");
        request.setCookies(new Cookie("token", "123456"));
        request.setParameter("name", "zhangsan");
        request.setParameter("age", "18");
        request.setContent("""
                    {
                        "name":"李四",
                        "age":20
                    }"""
                .getBytes(StandardCharsets.UTF_8));
        return new StandardServletMultipartResolver().resolveMultipart(request);
    }


    static class Controller {
        public void test(@RequestParam("name1") String name1,
                         String name2,
                         @RequestParam("age") int age,
                         @RequestParam(name = "home", defaultValue = "${JAVA17_HOME}") String home1,
                         @RequestParam("file") MultipartFile file,
                         @PathVariable("id") int id,
                         @RequestHeader("Content-Type") String header,
                         @CookieValue("token") String token,
                         @Value("${JAVA17_HOME}") String home2,
                         HttpServletRequest request,
                         @ModelAttribute User user1,
                         User user2,
                         @RequestBody User user3
        ) {
        }
    }
    static class User {
        private String name;
        private int age;
        public String getName() {
            return name;
        }

        public void setName(String name) {this.name = name;}

        public int getAge() {return age;}

        public void setAge(int age) {this.age = age;}

        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }


}
